﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace CppIDE.UI
{

    /// <summary>
    /// A használt szerkesztőmező
    /// </summary>
    public partial class SyntaxEdit : RichTextBox
    {

        /// <summary>
        /// Konstruktor, létrehoz egy új példányt.
        /// </summary>
        public SyntaxEdit()
        {
            TabStop = true;
            WordWrap = false;
            Font = new Font(FontFamily.GenericMonospace, 10F);
        }

        /// <summary>
        /// A leütött billentyűk kezelését végző függvény. Azért kell, mert
        /// a tab billentyűre nem hívodik meg az OnKeyPress esemény, de ez igen.
        /// Itt ellenőrizzük, hogy a leütött billentyű a tab-e és ha igen, akkor
        /// meghívjuk az onTab függvényt.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            const int WM_KEYDOWN = 0x100;
            const int WM_SYSKEYDOWN = 0x104;

            if ((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN))
            {
                switch (keyData)
                {
                    case Keys.Tab:
                        if (SelectionLength == 0)
                        {
                            onTab();
                            return true;
                        }
                        break;
                }
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        /// <summary>
        /// Tab leütésekor meghívott függvény: beszúr két szóközt az aktuális
        /// pozicióhoz.
        /// </summary>
        protected void onTab()
        {
            InsertText("  ");
        }

        /// <summary>
        /// Whitespace-e a kapott karakter?
        /// </summary>
        /// <param name="ch">a kérdéses karakter</param>
        /// <returns>igaz, ha a kapott karakter whitespace</returns>
        protected bool isWhiteSpace(char ch)
        {
            return (ch == ' ') || (ch == '\n') || (ch == '\r') || (ch == '\t');
        }

        /// <summary>
        /// Visszaadja a kurzor előtti utolsó nem whitespace karaktert az előző
        /// sorban. Ha ilyen nincs, akkor null-al tér vissza.
        /// </summary>
        /// <returns>az utolsó nem whitespace karakter az előző sorban (lehet null is)</returns>
        protected char? getLastNonWSInPrevLine()
        {
            int pos = SelectionStart;
            int newLineCount = 0;
            while (pos > 0 && isWhiteSpace(Text[--pos]))
            {
                if (Text[pos] == '\n')
                {
                    if (++newLineCount == 2)
                    {
                        return null;
                    }
                }
            }
            return Text[pos];
        }

        /// <summary>
        /// Visszaadja az utolsó sor tartalmát.
        /// </summary>
        /// <returns>az utolsó sor tartalma</returns>
        protected string getLastLine()
        {
            int pos = SelectionStart-1;
            string line = "";
            while (pos > 0 && (Text[--pos] != '\n')) {
                line = Text[pos] + line;
            }
            return line;
        }
        
        /// <summary>
        /// Visszaadja az utolsó sor behúzását
        /// </summary>
        /// <returns></returns>
        protected string getLastLineIndent()
        {
            string lastLine = getLastLine();
            string indent = "";
            int pos = 0;
            int length = lastLine.Length;
            while ((pos < length) && (lastLine[pos] == ' ')) {
                indent += lastLine[pos++];
            }
            return indent;
        }

        /// <summary>
        /// Ha a szöveg megváltozott, ez hívódik meg: ha egy
        /// blokkzáró karakter lett leütve, akkor kijjebb
        /// húzunk a behúzásból.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            try
            {
                if (Text[SelectionStart - 1] == '}')
                {
                    String lastLine = getLastLine();
                    if (lastLine.EndsWith("  "))
                    {
                        int savedSelectionStart = SelectionStart;
                        int savedSelectionLength = SelectionLength;
                        paint = false;
                        Select(SelectionStart - 3, 2);
                        SelectedText = "";
                        if (savedSelectionStart >= 0)
                        {
                            Select(savedSelectionStart - 2, savedSelectionLength);
                        }
                        paint = true;
                    }
                }
            }
            catch (Exception)
            {
            }
            paint = false;
            colorizeCurrentLine();
            paint = true;
            base.OnTextChanged(e);
        }

        /// <summary>
        /// Egy billentyű leütésekor ez hajtódik végre: ha az enter
        /// volt, és egy blokk-kezdő jel után, akkor beljebb húzunk
        /// a behúzásból. Ha nem is volt blokk-kezdő, de enter, akkor
        /// lemásoljuk az utolsó sor behúzását. (lásd: indent mód)
        /// </summary>
        /// <param name="e"></param>
        override protected void OnKeyPress(KeyPressEventArgs e)
        {
            if (e.KeyChar == 13)
            {
                InsertText(getLastLineIndent());
                if (getLastNonWSInPrevLine() == '{')
                {
                    InsertText("  ");
                }
            }
        }

        /// <summary>
        /// Bezárja a szöveget a megadott pozícióba.
        /// </summary>
        /// <param name="s"></param>
        private void InsertText(string s)
        {
            this.SelectedText = s;
        }

        /// <summary>
        /// A megadott sorhoz ugrik, és kijelöli azt.
        /// </summary>
        /// <param name="lineNumber">a keresett sor</param>
        public void gotoLine(int lineNumber)
        {
            int position = GetFirstCharIndexFromLine(lineNumber-1);
            int lineLength = Lines[lineNumber-1].Length;
            SelectionStart = position;
            SelectionLength = lineLength;
        }

        /// <summary>
        /// Csak akkor rajzolunk, ha true az értéke.
        /// </summary>
        private bool paint = true;

        /// <summary>
        /// Felüldefiniáljuk a WndProc metódust, hogy csak akkor rajzoljunk,
        /// ha a paint értéke true. Így nem fog villogni a mező, amíg a
        /// szintaxis-kiemelést végezzük (hiszen addig kikapcsoljuk a paint-et).
        /// </summary>
        protected override void WndProc(ref Message message)
        {
            const short WM_PAINT = 0x00f;
            if (message.Msg != WM_PAINT || paint)
            {
                base.WndProc(ref message);
            } else {
                message.Result = IntPtr.Zero;
            }
        }

        /// <summary>
        /// A teljes tartalmat kiszínezi
        /// </summary>
        public void colorizeFull()
        {
            int savedSelectionStart = SelectionStart;
            int savedSelectionLength = SelectionLength;
            paint = false;
            int linesCount = Lines.Length;
            for (int i = 0; i<linesCount; i++)
            {
                SelectionStart = GetFirstCharIndexFromLine(i);
                colorizeCurrentLine();
            }
            if (savedSelectionStart >= 0)
            {
                Select(savedSelectionStart, savedSelectionLength);
            }
            paint = true;
        }

        /// <summary>
        /// Kiszínezi az aktuális sort
        /// </summary>
        private void colorizeCurrentLine()
        {
            int savedSelectionStart = SelectionStart;
            int savedSelectionLength = SelectionLength;

            int lineBegin = savedSelectionStart;
            while ((lineBegin > 0) && (Text[lineBegin - 1] != '\n'))
            {
                lineBegin--;
            }
            int lineEnd = savedSelectionStart;
            while ((lineEnd < Text.Length) && (Text[lineEnd] != '\n'))
            {
                lineEnd++;
            }
            string line = Text.Substring(lineBegin, lineEnd - lineBegin);
            Select(lineBegin, lineEnd - lineBegin);
            SelectionColor = Color.Black;

            if (Regex.IsMatch(line, @"^\s*#"))
            {
                Select(lineBegin, lineEnd - lineBegin);
                SelectionColor = Color.Brown;
            }
            else
            {
                String keyWordPattern =
                    "(^|\\W)(const|double|float|int|short|struct|unsigned|break|continue|" +
                    "else|for|long|signed|switch|void|case|default|enum|sizeof|typedef|" +
                    "char|do|extern|if|return|static|union|while|bool|dynamic_cast|" +
                    "namespace|reinterpret_cast|try|new|static_cast|catch|false|operator|" +
                    "template|typename|class|friend|private|this|using|const_cast|inline|" +
                    "public|throw|virtual|delete|protected|true)(?=(\\W|$))";
                MatchCollection kwMatches = Regex.Matches(line, keyWordPattern, RegexOptions.Compiled);
                foreach (Match match in kwMatches)
                {
                    Group group = match.Groups[2];
                    Select(group.Index + lineBegin, group.Length);
                    SelectionColor = Color.Blue;
                }

                String numberPattern =
                    "(^|\\W)([\\d\\.]+)(?=(\\W|$))";
                MatchCollection numberMatches = Regex.Matches(line, numberPattern, RegexOptions.Compiled);
                foreach (Match match in numberMatches)
                {
                    Group group = match.Groups[2];
                    Select(group.Index + lineBegin, group.Length);
                    SelectionColor = Color.DarkOrange;
                }

                string strPattern = "\"([^\"]+)\"";
                MatchCollection strMatches = Regex.Matches(line, strPattern, RegexOptions.Compiled);
                foreach (Match match in strMatches)
                {
                    Select(match.Index + lineBegin, match.Length);
                    SelectionColor = Color.CadetBlue;
                }

                string oneLineCommentPattern = "//.*";
                Match oneLineCommentMatch = Regex.Match(line, oneLineCommentPattern, RegexOptions.Compiled);
                if (oneLineCommentMatch != null)
                {
                    Select(oneLineCommentMatch.Index + lineBegin, oneLineCommentMatch.Length);
                    SelectionColor = Color.Gray;
                }
            }
            if (savedSelectionStart >= 0)
            {
                Select(savedSelectionStart, savedSelectionLength);
            }
        }

    }

}
